#ifndef RAY_TRACE_PRO_SCENE_H
#define RAY_TRACE_PRO_SCENE_H

#include "Basic/GxLibBasic.h"
#include "Geometry.h"
#include "Material.h"
#include "KdTree.h"
#include "Light.h"
#include "EnvironmentShader.h"

using namespace GxLibBasic;

namespace RayTracePro
{
	class GeometryTracer
	{
	public:
		RayTraceSystem * system;
		inline bool Trace(DifferentialGeometry & inter, Geometry * geom, const Ray & ray, const RayDifferential & diffIn, float & t) const
		{
			Ray transformedRay;
			geom->InverseTransformation.TransformNormal(transformedRay.Direction, ray.Direction);
			geom->InverseTransformation.Transform(transformedRay.Origin, ray.Origin);
			transformedRay.tMin = ray.tMin;
			transformedRay.tMax = ray.tMax;
			//if (abs(transformedRay.Direction.x) > Epsilon)
				transformedRay.ReciprocalDirection.x = 1.0f / transformedRay.Direction.x;
			//if (abs(transformedRay.Direction.y) > Epsilon)
				transformedRay.ReciprocalDirection.y = 1.0f / transformedRay.Direction.y;
			//if (abs(transformedRay.Direction.z) > Epsilon)
				transformedRay.ReciprocalDirection.z = 1.0f / transformedRay.Direction.z;
			bool rs = geom->ComputeIntersection(system, inter, geom, transformedRay, diffIn, t);
			if (rs)
			{
				Vec3 v = inter.Position;
				geom->Transformation.Transform(inter.Position, v);
				v = inter.Normal;
				geom->InverseTransformation.TransposeTransformNormal(inter.Normal, v);
			}
			return rs;
		}
	};

	class GeometryTracerPred
	{
	public:
		RayTraceSystem * system;
		inline bool Trace(DifferentialGeometry & inter, Geometry * geom, const Ray & ray, const RayDifferential & diffIn, float & t) const
		{
			Ray transformedRay;
			geom->InverseTransformation.TransformNormal(transformedRay.Direction, ray.Direction);
			geom->InverseTransformation.Transform(transformedRay.Origin, ray.Origin);
			transformedRay.tMin = ray.tMin;
			transformedRay.tMax = ray.tMax;
			if (abs(transformedRay.Direction.x) > Epsilon)
				transformedRay.ReciprocalDirection.x = 1.0f / transformedRay.Direction.x;
			if (abs(transformedRay.Direction.y) > Epsilon)
				transformedRay.ReciprocalDirection.y = 1.0f / transformedRay.Direction.y;
			if (abs(transformedRay.Direction.z) > Epsilon)
				transformedRay.ReciprocalDirection.z = 1.0f / transformedRay.Direction.z;
			return geom->ComputeIntersectionPred(system, geom, transformedRay);
		}
	};

	class PhotonMap;

	class PhotonMapRadiusModifier
	{
	public:
		enum ModifierType
		{
			Set, Multiplier
		} Modifier;
		float Value;
		PhotonMapRadiusModifier()
		{
			Modifier = Multiplier;
			Value = 1.0f;
		}
		float GetRadius(float estimate)
		{
			if (Modifier == Set)
				return Value;
			else
				return Value * estimate;
		}
	};

	class Scene
	{
		friend class PhotonMapEvaluator;
	private:
		Array<RefPtr<Geometry>> geometryHolders;
		Array<Geometry*, true> geometries;
		Array<RefPtr<Material>> materials;
		KdTree<Geometry*> kdTree;
		RayTraceSystem * system;
		Array<RefPtr<Light>> lights;
		Array<Light *> lightPtrs;
		PhotonMap * globalPhotonMap;
		float globalPhotonMapRadius;
		void PrecomputeGI();
	public:
		PhotonMapRadiusModifier GlobalPhotonMapRadianceModifier;
		RefPtr<EnvironmentShader> Environment;
		
		Scene(RayTraceSystem * system)
		{
			this->system = system;
			this->globalPhotonMap = 0;
		}
		~Scene();

		RayTraceSystem * GetSystem()
		{
			return system;
		}

		inline float GetGlobalPhotonMapRadius()
		{
			return globalPhotonMapRadius;
		}

		void PrepareRendering(int maxDepth);
		Light * CreateLight()
		{
			Light * light = new Light();
			lights.Add(light);
			lightPtrs.Add(light);
			return light;
		}
		void AddGeometry(Geometry * geom, bool hold = true)
		{
			geometries.Add(geom);
			if (hold)
				geometryHolders.Add(geom);
		}
		
		inline PhotonMap * GetGlobalPhotonMap()
		{
			return globalPhotonMap;
		}

		void AddMaterial(Material * mat)
		{
			materials.Add(mat);
		}

		Material * CreateMaterial()
		{
			Material * mat = system->CreateMaterial();
			AddMaterial(mat);
			return mat;
		}

		inline bool TraceRayCore(DifferentialGeometry & inter, const Ray & ray, const RayDifferential & diffIn, float & t)
		{
			GeometryTracer tracer;
			tracer.system = system;
			return TraverseKdTree<Geometry*, GeometryTracer, false, false>(system, tracer, inter, kdTree, ray, diffIn, t);
		}

		inline bool TraceRayPredCore(float & t, const Ray & ray)
		{
			DifferentialGeometry inter;
			RayDifferential diff;
			GeometryTracerPred tracer;
			tracer.system = system;
			return TraverseKdTree<Geometry*, GeometryTracerPred, true, false>(system, tracer, inter, kdTree, ray, diff, t);
		}

		inline bool TraceRayPacketCore(DifferentialGeometry & inter, const RayPacket & rays, RayDifferentialPacket & diffIn, float & t)
		{
			//GeometryTracer tracer;
			//tracer.system = system;
			//return TraverseKdTree<Geometry*, GeometryTracer, false, false>(system, tracer, inter, kdTree, ray, diffIn, t);
		}

		inline void Shade(Vec4 & rs, DifferentialGeometry & inter, const Ray & ray, const RayDifferential & diff, float t, int depth)
		{
			if (inter.Material)
			{
				inter.Material->Shade(&rs, system, &inter, &ray, &diff, this, depth);
			}
			else
			{
				rs.w = 1.0f;
				rs.x = rs.y = rs.z = 1.0f;
			}
		}

		void GetLights(const GxLibMath::Vec3 & position, Light ** & lights, int & count)
		{
			lights = lightPtrs.Buffer();
			count = lightPtrs.Count();
		}
	};
}

#endif